package vip.xiaonuo.sys.core.aop;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import vip.xiaonuo.core.annotion.Decrypt;
import vip.xiaonuo.core.annotion.Encrypt;
import vip.xiaonuo.core.context.constant.ConstantContextHolder;
import vip.xiaonuo.core.pojo.response.ResponseData;
import vip.xiaonuo.core.util.AesUtil;
import vip.xiaonuo.core.util.RsaUtil;
import vip.xiaonuo.sys.core.cache.PublicKeyCache;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Collection;

/**
  *  加密解密AOP切面
  *
  * @author zhangbixi
  * @date 2021/7/27 16:56
  */
@Aspect
public class EncryptAndDecryptAop {
    @Autowired
    private PublicKeyCache publicKeyCache;

    /**
     * Pointcut 切入点
     * 匹配带注解的所有方法
     */
    @Pointcut("@annotation(vip.xiaonuo.core.annotion.Decrypt)||@annotation(vip.xiaonuo.core.annotion.Encrypt)")
    public void encryAndDecryAop() {
    }

    /**
     * 环绕通知
     */
    @Around(value = "encryAndDecryAop()")
    public Object around(ProceedingJoinPoint pjp) {
        try{
        //获取加解密配置参数(是否开启加解密)
        Boolean RSAFlag = ConstantContextHolder.getRSAFlag();

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        //request对象
        HttpServletRequest request = attributes.getRequest();

        //http请求方法  post get
        String httpMethod = request.getMethod().toLowerCase();

        //method方法
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();

        //method方法上面的注解
        Annotation[] annotations = method.getAnnotations();

        //方法的形参参数
        Object[] args = pjp.getArgs();

            //是否有@Decrypt
        boolean hasDecrypt = false;
        boolean hasEncrypt = false;
        for (Annotation annotation : annotations) {
            if (annotation.annotationType() == Decrypt.class) {
                hasDecrypt = true;
            }
            if (annotation.annotationType() == Encrypt.class) {
                hasEncrypt = true;
            }
        }
        //jackson
        ObjectMapper mapper = new ObjectMapper();
        //jackson 序列化和反序列化 date处理
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

        //拦截处理get请求,有解密注解并配置解密开启
        if ("get".equals(httpMethod) && hasDecrypt && RSAFlag) {
            //AES加密后的数据
            String data = request.getParameter("data");
            //params传参会把＋号变成空格，替换一下
            data = data.replace(' ', '+');
            //后端RSA公钥加密后的AES的key
            String aesKey = request.getParameter("aesKey");
            //params传参会把＋号变成空格，替换一下
            aesKey = aesKey.replace(' ', '+');

            //后端私钥解密的到AES的key
            byte[] plaintext = RsaUtil.decryptByPrivateKey(Base64.decodeBase64(aesKey), RsaUtil.getPrivateKey());
            aesKey = new String(plaintext);

            System.out.println("解密出来的AES的key：" + aesKey);

            //AES解密得到明文data数据
            String decrypt = AesUtil.decrypt(data, aesKey);
            System.out.println("解密出来的data数据：" + decrypt);
            //设置到方法的形参中，目前只能设置只有一个参数的情况
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            if (args.length > 0) {
                //此处只处理了参数的第1个，所以只会接到一个参数，所以要求用一个参数或一个对象传参
                args[0] = mapper.readValue(decrypt, args[0].getClass());
            }
        }
        else {
            args = pjp.getArgs();
        }
        //执行并替换最新形参参数   PS：这里有一个需要注意的地方，method方法必须是要public修饰的才能设置值，private的设置不了
        Object o = pjp.proceed(args);

            //返回结果之前加密
            if (hasEncrypt && RSAFlag) {
                mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                //每次响应之前随机获取AES的key，加密data数据
                String key = AesUtil.getKey();
                System.out.println("AES的key：" + key);
                String dataString = mapper.writeValueAsString(o);
                System.out.println("需要加密的data数据：" + dataString);
                //对称加密数据
                String data = AesUtil.encrypt(dataString, key);
                System.out.println("加密后的data数据：" + data);

                //前端公钥
                String publicKey =null;
                if("get".equals(httpMethod)){
                    publicKey = request.getParameter("publicKey");
                }else {
                    Collection allKeys = publicKeyCache.getAllKeys();
                    publicKey =publicKeyCache.get("ENCRYPT").replace("\\n","\\\n");
                }
                System.out.println("取出的publicKey："+publicKey);
                //用前端的公钥来加密AES的key，并转成Base64
                String aesKey = Base64.encodeBase64String(RsaUtil.encryptByPublicKey(key.getBytes(), publicKey));

                System.out.println("加密后的key:"+aesKey);
                //转json字符串并转成Object对象，设置到Result中并赋值给返回值o
                o = ResponseData.success(mapper.readValue("{\"data\":\"" + data + "\",\"aesKey\":\"" + aesKey + "\"}", Object.class));
            }
            //返回
            return o;

        } catch (Throwable e) {
            System.err.println(pjp.getSignature());
            e.printStackTrace();
            return ResponseData.error(e.getClass()+":" + e.getMessage());
        }
    }

}
